package com.t.bricks.bussiness.controller.recharge;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;

import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestTemplate;

import com.github.wxpay.sdk.WXPay;
import com.t.bricks.bussiness.db.entity.recharge.Recharge;
import com.t.bricks.bussiness.service.recharge.WeChatPayService;
import com.t.bricks.bussiness.util.recharge.WXPayConfigCustom;
import com.t.bricks.model.Log;
import com.t.bricks.model.MsgEmity;
import com.t.bricks.utils.function.data.MoneyUtil;
import com.t.bricks.utils.function.data.StringUtil;
import com.t.bricks.utils.function.encode.MD5Util;
import com.t.bricks.utils.function.json.JacksonShell;
import com.t.bricks.utils.spring.ModuleUtil;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAConfig;
import com.wechat.pay.java.service.payments.jsapi.JsapiService;
import com.wechat.pay.java.service.payments.jsapi.model.Amount;
import com.wechat.pay.java.service.payments.jsapi.model.Payer;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayRequest;
import com.wechat.pay.java.service.payments.jsapi.model.PrepayResponse;

/**
 * 参考: https://blog.csdn.net/weixin_45865428/article/details/117807648
 * @author huangxinbian
 * 
 * 步骤:
 * 1.在公众号设置"业务域名"\"JS接口安全域名"\"网页授权域名" 如 "04demo.tomatomeatman.icu"
 * 2.在公众号设置"白名单",把域名指向的IP加入,如"04demo.tomatomeatman.icu"域名为"42.192.8.108"
 * 3.做个二维码,访问地址为: "http://04demo.tomatomeatman.icu/getWXOAuth2Code"
 * 4.用微信扫二维码,进入后点击支付
 * 
 * 逻辑:
 * 1.微信扫码访问http://xxx.icu/pay/wx/getWXOAuth2Code获得code,并设置跳转页,跳转页为 http://xxx.icu/pay/wx/wx-oauth-code-return
 * 2.自动跳转到http://xxx.icu/pay/wx/wx-oauth-code-return时得到openId, 然后跳转到支付业务页面,如http://xxx.icu/pay/wx/confirm?openid=" + openid
 * 3.进入/pay/wx/confirm页面后通过openId和付费信息组合成订单(如订单号"A123")提交到微信, 并建立签名
 * 4.得到签名后返回一个支付情况页面如:http://xxx.icu/wxpay,目的是让客户看到待支付的内容
 * 5.客户点击"支付"按键自动进入微信的支付密码录入,完成支付
 * 6.支付完成后跳转到查询订单支付情况查询页面按订单号查询订单结果(如按订单号"A123"查询),注意:跳转由http://xxx.icu/wxpay页面中的 js代码window.location.href 定义
 */
@Controller
@Api(value="商户App微信支付", tags={"商户App微信支付操作接口"})
@RequestMapping("/pay/wx/app/")
public class WxPayAppController {
	
	/**
	 * 微信关联APP在腾讯平台注册的APPID,同时设置在微信平台
	 * 注意:这是'平台审核通过的应用APPID',不是'公众号的appid'
	 */
	@Value("${app.wechat.app.id:}")
	private String appId;
	
//	/**
//	 * 微信关联APP在腾讯平台注册的应用市场上APP名字,同时设置在微信平台
//	 * 注意:这是'应用市场上的APP名字'
//	 */
//	@Value("${app.wechat.app.title:}")
//	private String appTitle;

	/**
	 * 商户号
	 */
	@Value("${app.wechat.mch.id:}")
	private String merchantId;

	/**
	 * 微信商户API密钥,同时设置在微信平台
	 */
	@Value("${app.wechat.mch.api:}")
	private String mchKey;
	
//	/**
//	 * 商户API私钥路径
//	 */
//	@Value("${app.wechat.app.mch.privateKeyPath:}")
//	private String privateKeyPath;
//	
//	/**
//	 * 商户证书序列号
//	 */
//	@Value("${app.wechat.app.mch.merchantSerialNumber:}")
//	private String merchantSerialNumber;
//
//	/**
//	 * 微信支付平台证书路径
//	 */
//	@Value("${app.wechat.app.wechatPayCertificatePath:}")
//	private String wechatPayCertificatePath;

	/**
	 * 微信关联APP在腾讯平台注册的APPID,同时设置在微信平台
	 */
	@Value("${app.wechat.app.id:}")
	private String wechatRelAppId;

	/**
	 * 微信关联APP在腾讯平台注册的APPID,同时设置在微信平台
	 */
	@Value("${app.wechat.app.key:}")
	private String wechatRelAppKey;

	/**
	 * 内部访问密钥,注意发布时必须修改
	 */
	@Value("${app.key:}")
	private String appKey;
	
	@Autowired
	private WeChatPayService weChatPayService;
	
	/**
	 * APP下单
	 * @param sFund
	 * @return
	 */
	@ApiOperation(value="APP下单", notes="返回:应用ID;充值记录编号;商户号;预支付交易会话ID;时间戳;二次签名")
	@ApiImplicitParams({
		@ApiImplicitParam(name="sFund", value="充值金额单位元", dataType="String", paramType="query", required=true)
	})
	@RequestMapping(method={RequestMethod.POST, RequestMethod.GET}, value="/confirm")
	@ResponseBody
	public MsgEmity confirm(String sFund) {
		if (null == sFund || "".equals(sFund.trim())) {
			return MsgEmity.err(9001, "未能获得充值金额");//没有获得充值金额
		}
		
		if (!MoneyUtil.isBigDecimal(sFund)) {
			return MsgEmity.err(9002, "获取的充值金额格式错误:", sFund);//没有获得充值金额
		}
		
		BigDecimal mFund = MoneyUtil.string2BigDecimal(sFund);
		if (!MoneyUtil.isPlus(mFund)) {
			return MsgEmity.err(9003, "充值金额必须是正数");//充值金额为负数
		}
		
		String sUserId = ModuleUtil.currentLoginUserId();
		if (null == sUserId || "".equals(sUserId)) {
			return MsgEmity.err(9004, "用户编号不能为空");
		}
		
		MsgEmity me = weChatPayService.createOrder(sUserId, mFund);
		if (!me.isSuccess()) {
			return me;
		}
		
		Recharge recharge = me.getData();
		
		Integer totalFee = Integer.valueOf(MoneyUtil.toCent(recharge.getmFund()));//元转分
		
		String sign = new StringBuilder(400)
				.append("appid=").append(appId)
				.append("&attach=微信充值订单").append(recharge.getsOrderNo())//
				.append("&body=").append(recharge.getsBody())
				//.append("&device_info=").append()//设备号,自定义参数，可以为请求支付的终端设备号等,非必填
				.append("&detail=订单详情").append(recharge.getsBody())//
				.append("&mch_id=").append(merchantId)
				.append("&nonce_str=").append(recharge.getsOrderNo())
				.append("&notify_url=none")//
				//.append("&openid=").append(recharge.getsOpenid())
				.append("&out_trade_no=").append(recharge.getsOrderNo())
				.append("&spbill_create_ip=").append(recharge.getsSpbillCreateIp())
				.append("&total_fee=").append(totalFee)
				.append("&trade_type=APP")
				.append("&key=").append(mchKey)//放最后
				.toString();
		sign = MD5Util.MD5(sign);
		
		//以xml结构发送时
		String paramXml = new StringBuilder(1000)
			.append("<xml>")
			.append("	<appid>").append(appId).append("</appid>")
			.append("	<attach>微信充值订单").append(recharge.getsOrderNo()).append("</attach>")
			.append("	<body>").append(recharge.getsBody()).append("</body>")
			.append("	<mch_id>").append(merchantId).append("</mch_id>")
			.append("	<detail>订单详情").append(recharge.getsBody()).append("</detail>")
			.append("	<nonce_str>").append(recharge.getsOrderNo()).append("</nonce_str>")
			.append("	<notify_url>none</notify_url>")
			//.append("	<openid>").append(recharge.getsOpenid()).append("</openid>")
			.append("	<out_trade_no>").append(recharge.getsOrderNo()).append("</out_trade_no>")
			.append("	<spbill_create_ip>").append(recharge.getsSpbillCreateIp()).append("</spbill_create_ip>")
			.append("	<total_fee>").append(totalFee).append("</total_fee>")
			.append("	<trade_type>APP</trade_type>")
			.append("	<sign>").append(sign).append("</sign>")
			.append("</xml>")
			.toString();
		
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_XML);//参数以xml进行发送
		
		RestTemplate restTemplate = new RestTemplate();
		
		//以UTF-8作为编码接收
		List<HttpMessageConverter<?>> httpMessageConverters = restTemplate.getMessageConverters();
		httpMessageConverters.stream().forEach(httpMessageConverter -> {
			if(httpMessageConverter instanceof StringHttpMessageConverter){
				StringHttpMessageConverter messageConverter = (StringHttpMessageConverter) httpMessageConverter;
				messageConverter.setDefaultCharset(Charset.forName("UTF-8"));
			}
		});
		
		//HttpEntity<MultiValueMap<String, Object>> httpEntity = new HttpEntity<MultiValueMap<String, Object>>(paramMap, headers);//以json结构发送时
		HttpEntity<String> httpEntity = new HttpEntity<>(paramXml, headers);
		ResponseEntity<String> exchange = restTemplate.postForEntity("https://api.mch.weixin.qq.com/pay/unifiedorder", httpEntity, String.class);
		
		String body = exchange.getBody();
		
		//Log.debug("小程序统一下单响应的内容:", body);
		if (body.contains("<return_code><![CDATA[FAIL]]></return_code>")) {
			return MsgEmity.err(9007, "统一下单失败,返回内容:", body);
		}
		
		if (!body.contains("<return_code><![CDATA[SUCCESS]]></return_code>")) {
			return MsgEmity.err(9008, "统一下单失败,未能获取预定格式,返回内容:", body);
		}
		
		if (!body.contains("<prepay_id>")) {
			return MsgEmity.err(9009, "统一下单失败,未能获取预定格式prepay_id,返回内容:", body);
		}
		
		String prepayId = StringUtil.subBetween(body, "<prepay_id><![CDATA[", "]]></prepay_id>");
		
		if (null == prepayId || "".equals(prepayId.trim())) {
			return MsgEmity.err(9010, "统一下单失败,截取的prepay_id为空,返回内容:", body);
		}
		
		long time = System.currentTimeMillis()/1000;
		sign = new StringBuilder(192)
			.append("appid=").append(appId)
			.append("&noncestr=").append(recharge.getsOrderNo())
			.append("&package=Sign=WXPay")
			.append("&partnerid=").append(merchantId)
			.append("&prepayid=").append(prepayId)
			.append("&timestamp=").append(time)
			.append("&key=").append(mchKey)//放最后
			.toString();
		sign = MD5Util.MD5(sign);
		
		return MsgEmity.success((Object)recharge.getsOrderNo(),
				appId, ";", recharge.getsOrderNo(), ";", merchantId, ";",
				prepayId, ";", String.valueOf(time), ";", sign);
	}
	
	/**
	 * 支付成功通知
	 * @param sOrderNo 充值订单号
	 * @return
	 */
	@ApiOperation(value="支付成功通知", notes="验证微信订单")
	@ApiImplicitParams({
		@ApiImplicitParam(name="sOrderNo", value="充值订单号", dataType="String", paramType="query", required=true)
	})
	@RequestMapping(method={RequestMethod.POST, RequestMethod.GET}, value="/success")
	@ResponseBody
	public MsgEmity paySuccess(String sOrderNo) {
		MsgEmity me = validOrder(sOrderNo);
		if (!me.isSuccess()) {
			return me;
		}

		return weChatPayService.changeState(3, sOrderNo);//返回订单
	}
	
	/**
	 * 查询订单
	 * @param orderNo
	 * @return
	 */
	@ApiOperation(value="查询订单", notes="查询微信订单")
	@RequestMapping(method={RequestMethod.GET}, value="/find/order")
	@ResponseBody
	public MsgEmity orderQuery(String sOrderNo) {
		return validOrder(sOrderNo);
	}
	
	/**
	 * 开发测试
	 * @param key 请求密钥
	 * @return
	 */
	@ApiOperation(value="开发测试", notes="必须输入访问密钥")
	@RequestMapping(method={RequestMethod.GET}, value="/test")
	@ResponseBody
	public MsgEmity test(String key) {
		if (null == key || "".equals(key) || !appKey.equals(key)) {
			return MsgEmity.err(9001, "密钥错误");
		}
		
		MsgEmity me = weChatPayService.createOrder("openid123", MoneyUtil.toBigDecimal("0.01"));
		if (!me.isSuccess()) {
			return me;
		}
		
		Recharge recharge = me.getData();

		return weChatPayService.changeState(3, recharge.getsOrderNo());
	}
	
	/**
	 * 验证订单
	 * @param sOrderNo
	 * @return
	 */
	private MsgEmity validOrder(String sOrderNo) {
		if (null == sOrderNo || "".equals(sOrderNo.trim())) {
			return MsgEmity.err(9001, "订单号缺失");
		}
		
		sOrderNo = sOrderNo.trim();
		
		Map<String, String> reqData = new HashMap<String, String>(4);
		reqData.put("appid", appId);
		reqData.put("mch_id", merchantId);
		reqData.put("nonce_str", UUID.randomUUID().toString().replaceAll("-", "")); // 随机字符串，长度要求在32位以内。
		reqData.put("out_trade_no", sOrderNo);

		WXPay wxPay = new WXPay(new WXPayConfigCustom(appId, merchantId, mchKey));// 创建sdk客户端
		Map<String, String> map = null;
		try {
			map = wxPay.orderQuery(reqData);
		} catch (Exception e) {
			Log.error("查询账单发生异常:", e.getMessage());
			return MsgEmity.err(9002, String.format("查询账单发生异常:%s", e.getMessage()));
		}
			
//		返回结果: {"transaction_id":"4200001184202109030102014873",
//		"nonce_str":"LGl7TvvhYdzczg3k","trade_state":"SUCCESS",
//		"bank_type":"OTHERS","openid":"opk_66kKbtasRXX7IiDgL23KOD4Y",
//		"sign":"0106EC9F44F5AF6AE297AFEA6333617D","return_msg":"OK",
//		"fee_type":"CNY","mch_id":"1612988251","cash_fee":"1",
//		"out_trade_no":"10000001","cash_fee_type":"CNY",
//		"appid":"wxc47d0dccec3c0d8e","total_fee":"1",
//		"trade_state_desc":"支付成功","trade_type":"JSAPI",
//		"result_code":"SUCCESS","attach":"","time_end":"20210903110726",
//		"is_subscribe":"Y","return_code":"SUCCESS"}
		
		//{nonce_str=MfeF6wRD4EpuAV5j, 
		//device_info=, trade_state=NOTPAY, out_trade_no=0C4EC3A7708E4D379F4A3DB51FC3D3EB, 
		//appid=wxa89cac8ca16a7a49, 
		//total_fee=3000, trade_state_desc=订单未支付, 
		//sign=50AEC31CBA566393A71816E5BD6FC12D, 
		//return_msg=OK, result_code=SUCCESS, 
		//mch_id=1619435221, return_code=SUCCESS}
		
		//当wxPay错误时,例如{return_msg=mch_id参数格式错误, return_code=FAIL}
		if ("FAIL".equals(map.get("return_code"))) {
			return MsgEmity.err(9003, String.format("请求信息错误,调用信息:%s", JacksonShell.toJSONString(map)));
		}
		
		if (map.containsKey("err_code_des")) {
			return MsgEmity.err(9004, String.format("支付失败,返回信息:%s", JacksonShell.toJSONString(map)));
		}
		
		if (!sOrderNo.equals(map.get("out_trade_no"))) {
			return MsgEmity.err(9005, String.format("返回内容的订单号与待查询订单号不符,请求订单号:'%s',返回订单号:'%s'", sOrderNo, map.get("out_trade_no")));
		}
		
		if (!"SUCCESS".equals(map.get("return_code"))) {
			return MsgEmity.err(9006, "查询交易情况失败(腾讯)");
		}
		
		if (!"SUCCESS".equals(map.get("result_code"))) {
			return MsgEmity.err(9007, "交易业务结果失败(腾讯)");
		}
		
		if (!"SUCCESS".equals(map.get("trade_state"))) {
			return MsgEmity.err(9008, "腾讯回应付款情况:", map.get("trade_state_desc"));
		}
		
		String sDate = map.get("time_end");// 20210903110726
		if (null != sDate && "".equals(sDate.trim())) {
			sDate = String.format("%s-%s-%s %s:%s:%s",
					sDate.substring(0, 4),
					sDate.substring(4, 6),
					sDate.substring(6, 8),
					sDate.substring(8, 10),
					sDate.substring(10, 12),
					sDate.substring(12));
		} else {
			sDate = "无";
		}
		
		Map<String, String> result = new HashMap<>(4);
		result.put("sOrderNo", map.get("out_trade_no"));
		result.put("sTransactionId", map.get("transaction_id"));
		result.put("iTotalFee", (new DecimalFormat("0.00")).format(Float.valueOf(map.getOrDefault("total_fee", "0")) / 100));//支付金额(元)
		result.put("queryInfo", JacksonShell.toJSONString(map));
		result.put("sState", map.get("trade_state_desc"));
		result.put("dPayTime", sDate);
		
		return MsgEmity.success(result, "订单查询成功");
	}
	
	public static void test1() {
		  /** 商户号 */
		String merchantId = "";
		  /** 商户API私钥路径 */
		String privateKeyPath = "";
		  /** 商户证书序列号 */
		String merchantSerialNumber = "";
		  /** 微信支付平台证书路径 */
		String wechatPayCertificatePath = "";
		Config config = new RSAConfig.Builder().merchantId(merchantId)
				.privateKeyFromPath(privateKeyPath)
				.merchantSerialNumber(merchantSerialNumber)
				.wechatPayCertificatesFromPath(wechatPayCertificatePath)
				.build();
		JsapiService service = new JsapiService.Builder().config(config)
				.build();
		// request.setXxx(val)设置所需参数，具体参数可见Request定义
		PrepayRequest request = new PrepayRequest();
		Amount amount = new Amount();
		amount.setTotal(100);
		request.setAmount(amount);
		request.setAppid("wxa9d9651ae******");
		request.setMchid("190000****");
		request.setDescription("测试商品标题");
		request.setNotifyUrl("https://notify_url");
		request.setOutTradeNo("out_trade_no_001");
		Payer payer = new Payer();
		payer.setOpenid("oLTPCuN5a-nBD4rAL_fa********");
		request.setPayer(payer);
		PrepayResponse response1 = service.prepay(request);
		System.out.println(response1.getPrepayId());
	}

//	/**
//	 * 验证订单
//	 * @param sOrderNo
//	 * @return
//	 */
//	public MsgEmity test2(String sOrderNo) {
//		if (null == sOrderNo || "".equals(sOrderNo.trim())) {
//			return MsgEmity.err(9001, "订单号缺失");
//		}
//
//		sOrderNo = sOrderNo.trim();
//
//		QueryOrderByIdRequest queryRequest = new QueryOrderByIdRequest();
//		queryRequest.setMchid("190000****");
//		queryRequest.setTransactionId("4200001569202208304701234567");
//
//		Config config = new RSAConfig.Builder().merchantId(merchantId)
//				.privateKeyFromPath(privateKeyPath)
//				.merchantSerialNumber(merchantSerialNumber)
//				.wechatPayCertificatesFromPath(wechatPayCertificatePath)
//				.build();
//		
//		try {
//			JsapiService service = new JsapiService.Builder().config(config).build();
//			Transaction result = service.queryOrderById(queryRequest);
//			System.out.println(result.getTradeState());
//			return MsgEmity.success(result, "订单查询成功");
//		} catch (ServiceException e) {
//			// API返回失败, 例如ORDER_NOT_EXISTS
//			System.out.printf("code=[%s], message=[%s]\n", e.getErrorCode(),
//					e.getErrorMessage());
//			System.out.printf("reponse body=[%s]\n", e.getResponseBody());
//			return MsgEmity.err(1001, "订单查询失败");
//		}
//
//	}
	
	/**
	 * APP请求微信用户唯一标识
	 * @param code
	 * @return
	 */
	@ApiOperation(value="APP请求微信用户唯一标识", notes="查询微信用户唯一标识")
	@ApiImplicitParams({
		@ApiImplicitParam(name="code", value="请求凭证,由腾讯赋予5分钟有效", dataType="String", paramType="query", required=true)
	})
	@RequestMapping(method={RequestMethod.POST}, value="/unionid")
	@ResponseBody
	public MsgEmity smallGetUnionid(String code) {
		if (null == code || "".equals(code.trim())) {
			return MsgEmity.err(1001, "code参数为空");
		}
		
		String url = String.format("https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
				wechatRelAppId,//微信公众号的appid,同时设置在微信平台
				wechatRelAppKey,//微信公众号的appsecret,同时设置在微信平台
				code
		);

		RestTemplate restTemplate = new RestTemplate();
		ResponseEntity<String> exchange = restTemplate.exchange(url, HttpMethod.GET, null, String.class);
		
		//{"access_token": "63_VdDavvvvvvviqF88CQXaiWZp3i6K81eIrI_rbwPiMhxJ0f3nBij8qoIWZKBE7EeilNAgpOoQfallXT5RewMKnUWjj91fW2yS27pV2c","expires_in": 7200,"refresh_token": "63_aR_nNzHvvvvvv8V_JePmYTP3PIxgCz_EJT-GIb0P7zR7T6vvvvvavAYqAuiiwDGwvAz66o2bzzs49hrR2KBt-CkFfvj_s2Si9fQ","openid": "of46a5gpgKvvvvvianrdsIDo","scope": "snsapi_userinfo","unionid": "ogHdAsyvvvvAMTCLs-o"}
		//{"errcode": 40029,"errmsg": "invalid code, rid: 639c07f1-2fd22298b-499d3a32"}
		String body = exchange.getBody();
		Map<String, Object> map = JacksonShell.toJavaObject(body, Map.class);
		if (null == map) {
			return MsgEmity.err(1002, "请求失败");
		}
		
		if (map.containsKey("errcode")) {
			return new MsgEmity(false, "获取调用unionid失败", map);
		}
		
		if (!map.containsKey("unionid")) {
			Log.error("App请求unionid返回:", body);
			return MsgEmity.err(1003, "返回结果与预定结构不符");
		}
		
		if (!map.containsKey("unionid")) {
			Log.error("App请求unionid返回:", body);
			return MsgEmity.err(1003, "返回结果与预定结构不符");
		}
		
		return MsgEmity.success(map.get("unionid"), "获取unionid成功");
	}
	
}
